#include "_public.h"
#include "_ftp.h"
#include <iostream>
using namespace std;

struct st_arg{
    char host[31];           // 远程服务器的IP和端口
    int  mode;               // 传输模式，1-被动模式，2-主动模式，缺省采用被动模式
    char username[31];       // 远程服务器ftp的用户名
    char password[31];       // 远程服务器ftp的密码
    char remotepath[301];    // 远程服务器存放文件的目录
    char localpath[301];     // 本地文件存放的目录
    char matchname[101];     // 待上传文件匹配的规则
    char listfilename[301];  // 上传前列出客户端文件名的文件
    int  ptype;              // 上传后客户端文件的处理方式：1-什么也不做；2-删除；3-备份
    char localpathbak[301];  // 上传后客户端文件的备份目录
    char okfilename[301];    // 已上传成功文件名清单
    int timeout;             // 进程心跳的超时时间
    char pname[51];          // 进程名，建议用"ftpputfiles_后缀"的方式
} starg;

// 文件信息的结构体
struct st_fileinfo
{
  char filename[301];   // 文件名
  char mtime[21];       // 文件时间
};

CLogFile logfile;
Cftp ftp;

vector<struct st_fileinfo> vlistfile1;    // 已上传成功文件名的容器，从okfilename中加载
vector<struct st_fileinfo> vlistfile2;    // 上传前列出客户端文件名的容器，从nlist文件中加载
vector<struct st_fileinfo> vlistfile3;    // 本次不需要上传的文件的容器
vector<struct st_fileinfo> vlistfile4;    // 本次需要上传的文件的容器

void EXIT(int sig);
void _help();
bool _xmltoarg(char *strxmlbuffer);
bool _ftpputfiles();
// 把localpath目录下的文件加载到容器vlistfile2中
bool LoadLocalFile();
// 加载okfilename文件内容到容器vlistfile1中
bool LoadOKFile();
// 比较vlistfile2和vlistfile1，得到vlistfile3和vlistfile4
bool CompVector();
// 把容器vlistfile3中的内容写入okfilename文件，覆盖之前的旧okfilename文件
bool WriteToOKFile();
// ptype==1，把上传成功的文件记录追加到okfilename文件中
bool AppendToOKFile(struct st_fileinfo *stfileinfo);

CPActive PActive;       // 进程心跳

int main(int argc, char *argv[]){
    if(argc != 3){
        _help();
        return -1;
    }

    // 处理程序的退出信号
    // CloseIOAndSignal();
    signal(SIGINT, EXIT);
    signal(SIGTERM, EXIT);

    // 打开日志文件
    if(logfile.Open(argv[1], "a+") == false){
        cout << "open logfile failed " << argv[1] << endl;
        return -1;
    }

    // 解析xml，得到程序运行的参数
    if(_xmltoarg(argv[2]) == false){
        return -1;
    }

    PActive.AddPInfo(starg.timeout, starg.pname);           // 把进程心跳信息写入共享内存

    // 登录ftp服务器
    if(ftp.login(starg.host, starg.username, starg.password, starg.mode) == false){
        logfile.Write("ftp.login(%s, %s, %s) failed.\n", starg.host, starg.username, starg.password, starg.mode);
        return -1;
    }
    logfile.Write("ftp.login success.\n");

    _ftpputfiles();

    ftp.logout();

    return 0;
}

void EXIT(int sig){
    cout << "sig = " << sig << endl;
    exit(0);
}

void _help(){
    printf("\n");
    printf("Using:/project/tools1/bin/ftpputfiles logfilename xmlbuffer\n\n");

    printf("Sample:/project/tools1/bin/procctl 30 /project/tools1/bin/ftpputfiles /log/idc/ftpputfiles_surfdata.log \"<host>192.168.199.128:21</host><mode>1</mode><username>ly</username><password>1111</password><localpath>/tmp/idc/surdata</localpath><remotepath>/tmp/ftpputtest</remotepath><matchname>SURF_ZH*.JSON</matchname><ptype>1</ptype><localpathbak>/tmp/idc/surfdatabak</localpathbak><okfilename>/idcdata/ftplist/ftpputfiles_surfdata.xml</okfilename><timeout>80</timeout><pname>ftpputfiles_surfdata</pname>\"\n\n\n");

    printf("本程序是通用的功能模块，用于把远程ftp服务器的文件上传到本地目录。\n");
    printf("logfilename是本程序运行的日志文件。\n");
    printf("xmlbuffer为文件上传的参数，如下：\n");
    printf("<host>192.168.199.128:21</host> 远程服务器的IP和端口。\n");
    printf("<mode>1</mode> 传输模式，1-被动模式，2-主动模式，缺省采用被动模式。\n");
    printf("<username>ly</username> 远程服务器ftp的用户名。\n");
    printf("<password>1111</password> 远程服务器ftp的密码。\n");
    printf("<remotepath>/tmp/ftpputtest</remotepath> 远程服务器存放文件的目录。\n");
    printf("<localpath>/tmp/idc/surfdata</localpath> 本地文件存放的目录。\n");
    printf("<matchname>SURF_ZH*.XML,SURF_ZH*.CSV</matchname> 待上传文件匹配的规则。"\
         "不匹配的文件不会被上传，本字段尽可能设置精确，不建议用*匹配全部的文件。\n");
    printf("<ptype>1</ptype> 文件上传成功后，远程服务器文件的处理方式：1-什么也不做；2-删除；3-备份，如果为3，还要指定备份的目录。\n");
    printf("<localpathbak>/tmp/idc/surfdatabak</remotepathbak> 文件上传成功后，服务器文件的备份目录，此参数只有当ptype=3时才有效。\n");
    printf("<okfilename>/idcdata/ftplist/ftpputfiles_surfdata.xml</okfilename> 已上传成功文件名清单，此参数只有当ptype=1时才有效。\n");
    printf("<timeout>80</timeout> 上传文件超时时间，单位：秒，视文件大小和网络带宽而定。\n");
    printf("<pname>ftpputfiles_surfdata</pname> 进程名，尽可能采用易懂的、与其它进程不同的名称，方便故障排查。\n\n\n");
}

bool _xmltoarg(char *strxmlbuffer){
    memset(&starg, 0, sizeof(struct st_arg));
    GetXMLBuffer(strxmlbuffer, "host", starg.host, 30);             // 远程服务器的IP和端口
    if(strlen(starg.host) == 0){
        logfile.Write("host is null.\n");
        return false;
    }
    GetXMLBuffer(strxmlbuffer,"mode",&starg.mode);                  // 传输模式，1-被动模式，2-主动模式，缺省采用被动模式
    if (starg.mode!=2){
        starg.mode=1;
    }
    GetXMLBuffer(strxmlbuffer,"username",starg.username,30);        // 远程服务器ftp的用户名
    if (strlen(starg.username)==0){ 
        logfile.Write("username is null.\n");  
        return false; 
    }

    GetXMLBuffer(strxmlbuffer,"password",starg.password,30);        // 远程服务器ftp的密码
    if (strlen(starg.password)==0){ 
        logfile.Write("password is null.\n");  
        return false; 
    }

    GetXMLBuffer(strxmlbuffer,"remotepath",starg.remotepath,300);   // 远程服务器存放文件的目录
    if (strlen(starg.remotepath)==0){ 
        logfile.Write("remotepath is null.\n");  
        return false; 
    }

    GetXMLBuffer(strxmlbuffer,"localpath",starg.localpath,300);     // 本地文件存放的目录
    if (strlen(starg.localpath)==0){ 
        logfile.Write("localpath is null.\n");  
        return false; 
    }

    GetXMLBuffer(strxmlbuffer,"matchname",starg.matchname,100);     // 待上传文件匹配的规则
    if (strlen(starg.matchname)==0){ 
        logfile.Write("matchname is null.\n");  
        return false; 
    }

    
    GetXMLBuffer(strxmlbuffer,"ptype",&starg.ptype);                // 上传后客户端文件的处理方式：1-什么也不做；2-删除；3-备份。
    if ((starg.ptype!=1) && (starg.ptype!=2) && (starg.ptype!=3)){ 
        logfile.Write("ptype is error.\n"); 
        return false; 
    }

    GetXMLBuffer(strxmlbuffer,"localpathbak",starg.localpathbak,300); // 上传后客户端文件的备份目录。
    if ((starg.ptype==3) && (strlen(starg.localpathbak)==0)){ 
        logfile.Write("remotepathbak is null.\n");  
        return false; 
    }

    GetXMLBuffer(strxmlbuffer,"okfilename",starg.okfilename,300); // 已上传成功文件名清单。
    if ((starg.ptype==1) && (strlen(starg.okfilename)==0)){ 
        logfile.Write("okfilename is null.\n");  
        return false; 
    }

    GetXMLBuffer(strxmlbuffer,"timeout",&starg.timeout);   // 进程心跳的超时时间
    if (starg.timeout==0) { 
        logfile.Write("timeout is null.\n");  
        return false; 
    }

    GetXMLBuffer(strxmlbuffer,"pname",starg.pname,50);     // 进程名
    if (strlen(starg.pname)==0) { 
        logfile.Write("pname is null.\n");  
        return false; 
    }
    
    return true;
}

bool _ftpputfiles(){
    // 把localpath目录下的文件加载到容器vfilelist2中
    if(LoadLocalFile() == false){
        logfile.Write("LoadLocalFile() failed.\n");
        return false;
    }
    PActive.UptATime();

    if(starg.ptype == 1){
        // 加载okfilename文件内容到容器vlistfile1中
        LoadOKFile();
        // 调试代码，正式版本注释
        // for(int i = 0; i < vlistfile1.size(); ++i){
        //     logfile.Write("list 1 filename=%s=\n", vlistfile1[i].filename);
        // }

        // 比较vlistfile2和vlistfile1，得到vlistfile3和vlistfile4
        CompVector();
        // 调试代码，正式版本注释
        // for(int i = 0; i < vlistfile3.size(); ++i){
        //     logfile.Write("list 3 filename=%s=\n", vlistfile3[i].filename);
        // }
        // for(int i = 0; i < vlistfile4.size(); ++i){
        //     logfile.Write("list 4 filename=%s=\n", vlistfile4[i].filename);
        // }

        // 把容器vlistfile3中的内容写入okfilename文件，覆盖之前的旧okfilename文件
        WriteToOKFile();

        // 把vlistfile4中的内容复制到vlistfile2中（为了兼容后续代码）
        vlistfile2.clear();
        vlistfile2.swap(vlistfile4);
    }
    PActive.UptATime();

    char strremotefilename[301], strlocalfilename[301];
    // 遍历容器vlistfile2
    for(int i = 0; i < vlistfile2.size(); ++i){        
        SNPRINTF(strremotefilename, sizeof(strremotefilename), 300, "%s/%s", starg.remotepath, vlistfile2[i].filename);
        SNPRINTF(strlocalfilename, sizeof(strlocalfilename), 300, "%s/%s", starg.localpath, vlistfile2[i].filename);
        // 调用ftp.put()方法把文件上传到服务端，第三个参数填true的目的是确保文件上传成功，对方不可抵赖
        logfile.Write("put %s ...", strlocalfilename);
        if(ftp.put(strlocalfilename, strremotefilename, true) == false){
            logfile.WriteEx("failed.\n");
            break;
        }
        logfile.WriteEx("success.\n");
        PActive.UptATime();

        // ptype==1，把上传成功的文件记录追加到okfilename文件中
        if(starg.ptype == 1){
            AppendToOKFile(&vlistfile2[i]);
        }
        // 删除文件
        if(starg.ptype == 2){
            if (REMOVE(strlocalfilename)==false){
                logfile.Write("REMOVE(%s) failed.\n",strlocalfilename); 
                return false;
            }
        }
        // 转存到备份目录
        if(starg.ptype == 3){
            char strlocalfilenamebak[301];
            SNPRINTF(strlocalfilenamebak, sizeof(strlocalfilenamebak), 300, "%s/%s", starg.localpathbak, vlistfile2[i].filename);
            if (RENAME(strlocalfilename,strlocalfilenamebak)==false){
                logfile.Write("RENAME(%s,%s) failed.\n",strlocalfilename,strlocalfilenamebak); 
                return false;
            }
        }        
    }
    
    return true;
}

// 把ftp.nlist()方法获取到的list文件加载到容器vlistfile2中
bool LoadLocalFile(){
    vlistfile2.clear();

    CDir Dir;
    Dir.SetDateFMT("yyyymmddhh24miss");

    // 不包括子目录
    // 注意，如果本地目录下的总文件数超过10000，增量上传文件功能将有问题
    // 建议用deletefiles程序及时清理本地目录中的历史文件
    if(Dir.OpenDir(starg.localpath, starg.matchname) == false){
        logfile.Write("Dir.OpenDir(%s) failed.\n", starg.localpath);
        return false;
    }

    struct st_fileinfo stfileinfo;
    while(true){
        memset(&stfileinfo, 0, sizeof(struct st_fileinfo));
        // true表示删除文件名称最后的换行符
        if(Dir.ReadDir() == false){
            break;
        }
        strcpy(stfileinfo.filename, Dir.m_FileName);
        strcpy(stfileinfo.mtime, Dir.m_ModifyTime);

        vlistfile2.push_back(stfileinfo);
    }

    // 调试代码，正式版本删除
    // for(int i = 0; i < vlistfile2.size(); ++i){
    //     logfile.Write("list 2 filename=%s=\n", vlistfile2[i].filename);
    // }
    return true;
}

// 加载okfilename文件内容到容器vlistfile1中
bool LoadOKFile(){
    vlistfile1.clear();

    CFile File;
    if((File.Open(starg.okfilename,"r"))==false){
        return true;            // 这里返回true是因为第一次运行时okfilename为空
    }

    char strbuffer[501];
    struct st_fileinfo stfileinfo;
    
    while(true){
        memset(&stfileinfo, 0, sizeof(struct st_fileinfo));
        // true表示删除文件名称最后的换行符
        if(File.Fgets(strbuffer, 300, true) == false){
            break;
        }
        GetXMLBuffer(strbuffer, "filename", stfileinfo.filename);
        GetXMLBuffer(strbuffer, "mtime", stfileinfo.mtime);
        vlistfile1.push_back(stfileinfo);
    }
    return true;
}

// 比较vlistfile2和vlistfile1，得到vlistfile3和vlistfile4
bool CompVector(){
    vlistfile3.clear();
    vlistfile4.clear();

    int i = 0, j = 0;
    for(i = 0; i < vlistfile2.size(); ++i){
        for(j = 0; j < vlistfile1.size(); ++j){
            // 如果找到，把记录放入vlistfile3
            if( (strcmp(vlistfile2[i].filename, vlistfile1[j].filename) == 0) && (strcmp(vlistfile2[i].mtime, vlistfile1[j].mtime) == 0) ) {
                vlistfile3.push_back(vlistfile2[i]);
                break;
            }
        }
        // 如果未找到，把记录放入vlistfile4
        if(j == vlistfile1.size()){
            vlistfile4.push_back(vlistfile2[i]);
        }
    }
    return true;
}

// 把容器vlistfile3中的内容写入okfilename文件，覆盖之前的旧okfilename文件
bool WriteToOKFile(){
    CFile File;

    if(File.Open(starg.okfilename,"w")==false){
        logfile.Write("File.Open(%s) failed.\n", starg.okfilename);
        return false;
    }

    for(int i = 0; i < vlistfile3.size(); ++i){
        // File.Fprintf("%s\n", vlistfile3[i].filename);
        File.Fprintf("<filename>%s</filename><mtime>%s</mtime>\n", vlistfile3[i].filename, vlistfile3[i].mtime);
    }

    return true;
}

// ptype==1，把上传成功的文件记录追加到okfilename文件中
bool AppendToOKFile(struct st_fileinfo *stfileinfo){
    CFile File;

    if((File.Open(starg.okfilename,"a"))==false){
        logfile.Write("File.Open(%s) failed.\n", starg.okfilename);
        return false;
    }

    // File.Fprintf("%s\n", stfileinfo->filename);
    File.Fprintf("<filename>%s</filename><mtime>%s</mtime>\n", stfileinfo->filename, stfileinfo->mtime);
    
    return true;
}


